/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package helloscala.practise.chapter13

import helloscala.practise.ChapterEntry
import scala.annotation._
import helloscala.practise.chapter7.{Employee, Jeff, Winnie, Cameron}
import encodedstring._ //relative import
import newClickObserver._

object Chapter13Entry extends ChapterEntry {
  def runTests: Unit = {
    testNewClickObserver
  }

  def testNewClickObserver = {
    //try "receiveUpdate()" observer
    def clickButton(button: ButtonWithCallback, count: Int) = {
      for (i <- 1 to count)
        button.click
    }

    val button1 = new ButtonSubjectObserver.ObservableButton("button1")
    val button2 = new ButtonSubjectObserver.ObservableButton("button2")
    val buttonObserver = new ButtonClickObserver
    button1.addObserver(buttonObserver)
    button2.addObserver(buttonObserver)

    clickButton(button1, 3)
    clickButton(button2, 5)

    println(buttonObserver.countsOfButton(button1.name))
    println(buttonObserver.countsOfButton(button2.name))
//    val button = new ButtonWithCallback with ObservableClicks with Subject4ReceiveUpdate
//    val counter = new SelectiveCounter
//    button.addObserver(counter)
//    for (i <- 1 to 5) button.click
//    println("Button clicked [" + counter.count + "] times")
//
//    //try functional observer
//    val adButton = new ButtonWithCallback with ObservableClicks with Subject4Functional
//    var count = 0
//    adButton.addObserver((adButton) => count += 1) //observer is a function instead of object
//    for (i <- 1 to 3) adButton.click
//    println("Button clicked [" + count + "] times")
  }

  def testEncodedString = {
    def printTokens(es: EncodedString) = {
      println("EncodedString: ")
      es.toToken foreach(tok => println("token: " + tok))
    }

    val tsv = EncodedString("jeff-loves-winnie-and-cameron", EncodedString.Separator.DASH)
    printTokens(tsv)
  }

  def testNoneFilteredComprehension = {
    val oldList = List(Some(5), None)
    val newList = for {option <- oldList
      newOption = option.map((n: Int) => n * 5)
    } yield newOption
    println(newList)

//    def upper(strings: Option*): Seq[Option] = {
//      strings.map((s:String) => s.toUpperCase())
//    }
//    upper("I", "Am", "Jeff", None)
  }

  def testNoneStyleFilePrinter = {
    import java.io.File
    val scalaPrinter = new FilePrinterScalaOptimized(new File("d:/print2scala.txt"))
    scalaPrinter.printFile
  }

  def testEnum = {
    //enum is the lightweight version of sealed case class
    object HttpMethod extends Enumeration {
      type Method = Value
      val Post, Get = Value
    }

    import HttpMethod._
    def handleMethods(httpMethod: HttpMethod.Method) = httpMethod match {
      case Post => println("Post!")
      case Get => println("Get!")
    }

    //sealed case class don't have such api to traverse all possibilities
    HttpMethod foreach(method => handleMethods(method)) //deprecated
    HttpMethod.values.foreach(m => handleMethods(m))
  }

  def testScalaBuiltinAnnotations = {
    //@tailrec //fib() doesn't satisfy 'Tail-call', so compile error
    def fib(i: Int): Int = (i /*: @switch */) match {
      case _ if i <= 1 => i //with @switch, 'if i<=1' results in compile error
      case _ => fib(i-1) + fib(i-2)
    }
    println(fib(5))

    def factorial(i: BigInt): BigInt = {
      @tailrec //factorial() is tail-call, so no error on compile
      def fact(i: BigInt, accumulator: BigInt): BigInt = i match {
        case _ if i == 1 => accumulator
        case _ => fact(i - 1, i * accumulator)
      }
      fact(i, 1)
    }
    println(factorial(5))

    //@unchecked
    def matchJeff(person: Employee) = (person: @unchecked) match {//without @unchecked, compiler will warn when not exhaustive
      case Jeff(sal) => println("Jeff! $" + sal)
      case Winnie(sal) => println("Winnie! $" + sal)
      //missinf case of Cameron here
    }
    val jeff = new Jeff(1500)
    matchJeff(jeff)

    //@specialized. Performance optimizing purpose, change dynamic [Any] into static primitive type [Int],[Double],...
    //in 2.8.0 final @specialized already support (Unit, Boolean, Byte, Short, Char, Int, Long, Float, Double)
    //can see details in scala.runtime.AnyValCompanion.scala
    //
    //in following example, comiler will generate 3 copies of SpecializedTypedClass, for Double/Int/Long respectively
//    class SpecializedTypedClass[@specialized(Double, Int, Long) +UsualNum](val field1: UsualNum) //compile err in 2.8.1 final, fixed in 2.9.x. see bug:SI-3381 for details ...

    //@throws
    import java.io.File
    val scalaPrinter = new FilePrinterScala(new File("d:/print2scala"))
    scalaPrinter.print
  }

  def testScalaAnno = {
    implicit val value1 = "defaultValue1"
    implicit val value2 = Map("jz" -> "Jeff", "ww" -> "Winnie")
    class OneScalaAnno(val value1: String, val value2: Map[String, Any]) extends StaticAnnotation
    class TwoScalaAnno(value1: String, value2: String) extends StaticAnnotation
    class ThreeScalaAnno(val value1: String)(implicit val value2: Map[String, Any]) extends StaticAnnotation
    class NestedScalaAnno(value1: String) extends StaticAnnotation

    @NestedScalaAnno("nested") //since 2.8.0, nested annotation is allowed
    class MainScalaAnno(value1: String) extends StaticAnnotation

//    @ThreeScalaAnno("cameron")(Map("jz" -> "Jeff", "ww" -> "Winnie")) //error, curry constructor of annotation not supported yet in 2.8
//    @TwoScalaAnno("jeff", "winnie") //successful
    @OneScalaAnno("jeff", Map(("jz", "Jeff"), ("ww", "Winnie"))) //successful
    class C1

    val c = new C1
    val annos = classOf[C1].getAnnotations
    //unlike java anno, scala.StaticAnnotation does not support any runtime visibility
    //and there's no scala annotation to extend for runtime visible yet, the following
    //code will give you nothing in the result
    println(annos.size)
    println(annos.foreach(a => print(a)))
  }

  def testJavaAnnoOnScala = {
    @OneJavaAnno
    class Person(val id: Int, val name: String, @OneJavaAnno("<150") val age: Int) {
      @OneJavaAnno def fullName: String = name + " is fullname"
    }
    println(classOf[Person].getAnnotations.foreach(a => print(a)))
  }

  
}
